#include "QPair.h"
#include "utilities/utilities.h"
#include "Protocols/Dhcp.h"
#include "utilities/NetAddress.h"
#include "utilities/ReadablePacket.h"
#include "protocols/ProtocalCode.h"
#include "protocols/ProtocalPool.h"
#include "protocols/ShowableItem.h"
#include <QtCore/QSet>

bool DhcpHeader::mFirst = true;
QSet<int> DhcpHeader::mOpContainer;

REGISTER_PROTOCOL(DhcpProtocol)

int DhcpHeader::getOptionLength(const u_char* data) const
{
	if (mFirst)
	{
		for(int i = 1; i < 82; ++ i)
		{
			mOpContainer.insert(i);
		}
		mFirst = false;
	}

	if (mOpContainer.contains((int) (*data)))
	{
		return (int) (*(data + 1)) + 2;
	}
	else if (255 == (int) (*data))
	{
		return 3;
	}
	else
	{
		return 0;
	}
}

ShowableItem* DhcpHeader::getOptionInfo(const u_char* &data, int& pos,QPair<int, int> opInfo) const
{
	ShowableItem* opProto,* opTmp;
	QByteArray temp;
	int i = 0;

	switch(opInfo.first)
	{
	case 1:		
		opProto = createProtocalItem(QString("Subnet Mask (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Subnet Mask").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 3:
		opProto = createProtocalItem(QString("Routers (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Routers").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		while(i ++ < opInfo.second){
			opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
			opProto->pushChild(opTmp);
		}
		
		data += opInfo.second + 2;
		break;
	case 6:
		opProto = createProtocalItem(QString("Domain Name Servers (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Domain Name Servers").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 6).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 	12:
		opProto = createProtocalItem(QString("Host Name Address (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Host Name Address").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("String:", replaceZero((const char *)(data + 2), opInfo.second), pos, opInfo.second);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 44:
		opProto = createProtocalItem(QString("NetBIOS (TCP/IP) Name Servers (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", opInfo.first, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 6).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 50:
		opProto = createProtocalItem(QString("Requested IP Address (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:",QString("%1 Requested IP Address").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 51:
		opProto = createProtocalItem(QString("IP Address Lease Time (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second);

		opTmp = createItem("Option Code:", opInfo.first, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", (int)(*(data + 1)), pos, 1);
		opProto->pushChild(opTmp);

		temp = QByteArray::fromRawData((const char *)(data + 2), opInfo.second);
		opTmp = createItem("Value:", QString(temp), pos, (int)(*(data + 1)));
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 53:
		opProto = createProtocalItem(QString("Message Type (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second);

		opTmp = createItem("Option Code:",QString("%1 Message Type").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", (int)(*(data + 1)), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Message Type:", (int)(*(data + 2)), pos, opInfo.second);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 54:
		opProto = createProtocalItem(QString("Server Identifier (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Server Identifier").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_IPV4,data + 2).toString(), pos, 4,ShowableItem::ITEMICON_HOST);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 55:
		opProto = createProtocalItem(QString("Parameter Request List (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second);

		opTmp = createItem("Option Code:", QString("%1 Parameter Request List").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", (int)(*(data + 1)), pos, 1);
		opProto->pushChild(opTmp);

		i = 0;
		while(i < opInfo.second)
		{
			opTmp = createItem("Requested Option:", (int)(*(data + 2 + i)), pos, 1);
			opProto->pushChild(opTmp);
			i ++;
		}
		data += opInfo.second + 2;
		break;
	case 58:
	case 59:
	case 60:
		opProto = createProtocalItem(QString("Vendor Class Identifier (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second);

		opTmp = createItem("Option Code:", QString("%1 Vendor Class Identifier").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		temp = QByteArray::fromRawData((const char *)(data + 2), opInfo.second);
		opTmp = createItem("String:", QString(temp), pos, opInfo.second);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 61:
		opProto = createProtocalItem(QString("Client Identifier (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:", QString("%1 Client Identifier ").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Hardware Type:", (int)(*(data + 2)), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Address:", NetAddress(ADDR_MAC,data + 3).toString(), pos, 6,ShowableItem::ITEMICON_NETCARD);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 81:
		opProto = createProtocalItem(QString("Client FQDN (t=%1 l=%2)").arg(opInfo.first).arg(opInfo.second),pos, opInfo.second + 2);

		opTmp = createItem("Option Code:",QString("%1 Client FQDN").arg(opInfo.first), pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("Option Length:", opInfo.second, pos, 1);
		opProto->pushChild(opTmp);

		opTmp = createItem("String:", replaceZero((const char *)(data + 2), opInfo.second), pos, opInfo.second);
		opProto->pushChild(opTmp);

		data += opInfo.second + 2;
		break;
	case 255:
		opProto =createProtocalItem("DHCP Option End", pos, opInfo.second);

		opTmp = createItem("Option Code:",opInfo.first, pos, 1);
		opProto->pushChild(opTmp);

		data += opInfo.second;
		break;
	case 256:
		opProto = createItem("Data Area:", QString("(%1 bytes)").arg(opInfo.second), pos, opInfo.second);

		data += opInfo.second;
		break;
	default:
		opProto = NULL;
		break;
	}

	return opProto;
}


PacketHeader*
DhcpProtocol::analyzeProc(ReadablePacket* packet,int low,int high,Protocol*& tip)
{
	DhcpHeader* header = new DhcpHeader(DhcpProtocol_register.m_Instance,packet->getData(),low,high - low + 1);

	const u_char* data = (header->mData + header->getFormatLength() + 4); 
	while(data < (header->mData + header->mLength))
	{
		int i = header->getOptionLength(data);
		if (0 != i && 255 != (int)(*data))
		{
			header->mOpCode.push_back(QPair<int, int>((int)(*data), i - 2));
			data += i;
		}
		else if (255 == (int)(*data))
		{
			header->mOpCode.push_back(QPair<int, int>((int)(*data), 1));
			data += 1;
		}
		else
		{
			header->mOpCode.push_back(QPair<int,int>(256, (header->mData + header->mLength) - data));
			data = (header->mData + header->mLength);
		}
	}
	tip = NULL;

	return header;
}

QString DhcpHeader::getSummary() const
{
	return QString(" ");
}

void DhcpHeader::toShowableItems(ShowableItem* root) const
{
	HeaderFormat* header = (HeaderFormat*)mData;
	int pos = mPos;
	ShowableItem* tmp;
	QByteArray zero;
	
	ShowableItem* proto = createProtocalItem("BootP - Bootstrap Protocol [0-32]",pos, sizeof(HeaderFormat));
	root->pushChild(proto);
	
	tmp = createItem("Operation:",(int)header->_mOpcode, pos, 1);
	proto->pushChild(tmp);

	tmp = createItem(("Hardware Address Type:"), (int)header->_mHType, pos, 1);
	proto->pushChild(tmp);

	tmp = createItem("Hardware Address Length:", (int)header->_mHLen, pos, 1);
	proto->pushChild(tmp);

	tmp = createItem("Hops:", (int)header->_mHops, pos, 1);
	proto->pushChild(tmp);

	tmp = createItem("Transaction ID:", toHexString(header->_mTranID, 4), pos, 4);
	proto->pushChild(tmp);

	tmp = createItem("Seconds Since Boot Start:", (int)header->_mSeconds, pos, 2);
	proto->pushChild(tmp);

	tmp = createItem("BootP Flags:",toHexString((const u_char *)header->_mFlags, 2), pos, 2);
	proto->pushChild(tmp);

	tmp = createItem("IP Address Known By Client:",NetAddress(ADDR_IPV4,header->_mCtIPAddr).toString(), pos, 4);
	proto->pushChild(tmp);
	
	tmp = createItem("Client IP Addr Given By Srvr:", NetAddress(ADDR_IPV4, header->_mYrIPAddr).toString(), pos, 4);
	proto->pushChild(tmp);

	tmp = createItem("Server IP Address:", NetAddress(ADDR_IPV4, header->_mSrIPAddr).toString(), pos, 4);
	proto->pushChild(tmp);

	tmp =createItem("Gateway IP Address:",NetAddress(ADDR_IPV4, header->_mGyIPAddr).toString(), pos, 4);
	proto->pushChild(tmp);

	tmp = createItem("Client Hardware Addr:", NetAddress(ADDR_MAC, header->_mCtHAddr).toString(), pos, 6,ShowableItem::ITEMICON_NETCARD);
	proto->pushChild(tmp);

	tmp = createItem("Unused:", toHexString((const u_char*)header->_mCtHAddrPad, 10), pos, 10);
	proto->pushChild(tmp);
	

	tmp = createItem("Server Host Name:", replaceZero((const char *)header->_mSrHostName, 64), pos, 64);
	proto->pushChild(tmp);

	tmp = createItem("Boot File Name:",replaceZero((const char *)header->_mBootFilename, 128), pos, 128);
	proto->pushChild(tmp);

	const u_char* nowdata = mData + sizeof(HeaderFormat);
	ShowableItem* dhcp = createProtocalItem("DHCP - Dynamic Host Configuration Protocol ",pos, getHeaderLength() - sizeof(HeaderFormat));
	root->pushChild(dhcp);
	
	tmp = createItem("DHCP Magic Cookie:", toHexString(nowdata, 4), pos, 4);
	nowdata += 4;
	dhcp->pushChild(tmp);

	QVector<QPair<int, int> >::const_iterator iter = mOpCode.begin();
	while(iter < mOpCode.end())
	{
		ShowableItem* t = getOptionInfo(nowdata, pos, *iter);
		dhcp->pushChild(t);
		iter ++;
	}
}

DhcpProtocol::DhcpProtocol() : Protocol(DHCP,QString("DHCP"))
{
	shared_ptr<AppProtocolProperty> prop = make_shared<AppProtocolProperty>();
	prop->mDefaultPort.push_back(67);
	mInfo = prop;
}

QString DhcpHeader::replaceZero(const char * data, int length) const
{
	QString result = "";
	QChar ch = QChar::fromAscii(*data);

	for (int i = 0; i < length; ++ i)
	{
		ch = QChar::fromAscii(*(data + i));
		if (0 == (int)ch.toAscii())
		{
			ch = '.';
		}

		result.append(ch);
	}

	return result;
}

